001 /* 002 * Copyright 2004-2005 Niclas Hedhman 003 * Copyright 2005 Stephen McConnell 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. 015 * 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 package net.dpml.transit; 021 022 import java.io.IOException; 023 import java.rmi.NoSuchObjectException; 024 import java.rmi.Remote; 025 import java.rmi.RemoteException; 026 import java.rmi.server.UnicastRemoteObject; 027 import java.net.PasswordAuthentication; 028 import java.net.URL; 029 import java.util.Properties; 030 031 import net.dpml.transit.link.ArtifactLinkManager; 032 import net.dpml.transit.link.LinkManager; 033 import net.dpml.transit.model.CacheModel; 034 import net.dpml.transit.model.TransitModel; 035 import net.dpml.transit.model.ProxyModel; 036 import net.dpml.transit.model.ProxyListener; 037 import net.dpml.transit.model.ProxyEvent; 038 import net.dpml.transit.model.RequestIdentifier; 039 import net.dpml.transit.model.DisposalListener; 040 import net.dpml.transit.model.DisposalEvent; 041 import net.dpml.transit.monitor.LoggingAdapter; 042 043 import net.dpml.lang.UnknownKeyException; 044 import net.dpml.util.Logger; 045 046 047 /** 048 * The initial context of the transit system. 049 * 050 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 051 * @version 1.0.2 052 */ 053 public final class SecuredTransitContext 054 { 055 //------------------------------------------------------------------ 056 // static 057 //------------------------------------------------------------------ 058 059 /** 060 * Creation of the transit context. If the transit context has already 061 * been established the method returns the singeton context otherwise a new 062 * context is created relative to the authoritve url and returned. 063 * @param model the active transit model 064 * @return the secured transit context 065 * @exception TransitException if an error occurs during context creation 066 * @exception NullArgumentException if the supplied configration model is null 067 * and an instance of this class has not been created already. 068 */ 069 public static SecuredTransitContext create( TransitModel model ) 070 throws TransitException, NullArgumentException 071 { 072 synchronized( SecuredTransitContext.class ) 073 { 074 if( m_CONTEXT != null ) 075 { 076 return m_CONTEXT; 077 } 078 079 if( null == model ) 080 { 081 throw new NullArgumentException( "model" ); 082 } 083 084 Logger logger = resolveLogger( model ); 085 if( logger.isDebugEnabled() ) 086 { 087 logger.debug( "creating transit context" ); 088 } 089 090 try 091 { 092 m_CONTEXT = new SecuredTransitContext( model, logger ); 093 } 094 catch( TransitException e ) 095 { 096 throw e; 097 } 098 catch( Exception e ) 099 { 100 String error = "Unable to establish the transit context."; 101 throw new TransitException( error, e ); 102 } 103 104 return m_CONTEXT; 105 } 106 } 107 108 private static Logger resolveLogger( TransitModel model ) 109 { 110 if( model instanceof DefaultTransitModel ) 111 { 112 DefaultTransitModel m = (DefaultTransitModel) model; 113 return m.getLoggingChannel(); 114 } 115 else 116 { 117 return new LoggingAdapter(); 118 } 119 } 120 121 /** 122 * Return the singleton context. 123 * @return the secure context 124 */ 125 public static SecuredTransitContext getInstance() 126 { 127 synchronized( SecuredTransitContext.class ) 128 { 129 if( null == m_CONTEXT ) 130 { 131 throw new IllegalStateException( "context" ); 132 } 133 else 134 { 135 return m_CONTEXT; 136 } 137 } 138 } 139 140 //------------------------------------------------------------------ 141 // state 142 //------------------------------------------------------------------ 143 144 /** 145 * The configuration model. 146 */ 147 private TransitModel m_model; 148 149 /** 150 * The cache handler. 151 */ 152 private CacheHandler m_cacheHandler; 153 154 /** 155 * The LinkManager instance. 156 */ 157 private LinkManager m_linkManager; 158 159 /** 160 * Logging channel. 161 */ 162 private Logger m_logger; 163 164 private ProxyController m_proxyController; 165 166 private DisposalController m_disposalController; 167 168 //------------------------------------------------------------------ 169 // constructors 170 //------------------------------------------------------------------ 171 /** 172 * Creation of a new secured transit context. 173 * @param model the transit configuration model 174 * @param logger the assigned logging channel 175 * @exception IOException if an I/O error occurs 176 */ 177 private SecuredTransitContext( TransitModel model, Logger logger ) throws IOException 178 { 179 m_model = model; 180 m_logger = logger; 181 182 CacheModel cacheModel = model.getCacheModel(); 183 Logger cacheLogger = logger.getChildLogger( "cache" ); 184 DefaultCacheHandler cache = new DefaultCacheHandler( cacheModel, cacheLogger ); 185 m_cacheHandler = cache; 186 ProxyModel proxy = m_model.getProxyModel(); 187 if( null != proxy ) 188 { 189 synchronized( proxy ) 190 { 191 setupProxy(); 192 m_proxyController = new ProxyController(); 193 proxy.addProxyListener( m_proxyController ); 194 } 195 } 196 m_disposalController = new DisposalController(); 197 model.addDisposalListener( m_disposalController ); 198 } 199 200 //------------------------------------------------------------------ 201 // SecuredTransitContext 202 //------------------------------------------------------------------ 203 204 /** 205 * Return a layout object matching the supplied identifier. 206 * @param id the layout identifier 207 * @return the layout object 208 * @exception UnknownKeyException if the supplied layout id is unknown 209 * @exception IOException if an IO error occurs 210 */ 211 public Layout getLayout( String id ) throws UnknownKeyException, IOException 212 { 213 LayoutRegistry registry = m_cacheHandler.getLayoutRegistry(); 214 Layout layout = registry.getLayout( id ); 215 if( null == layout ) 216 { 217 throw new UnknownKeyException( id ); 218 } 219 else 220 { 221 return layout; 222 } 223 } 224 225 /** 226 * Return the cache layout. 227 * @return the layout 228 */ 229 public Layout getCacheLayout() 230 { 231 return getCacheHandler().getLayout(); 232 } 233 234 /** 235 * Return the cache handler. 236 * @return the cache handler 237 */ 238 public CacheHandler getCacheHandler() 239 { 240 return m_cacheHandler; 241 } 242 243 /** 244 * Return the link manager. 245 * @return the cache handler 246 */ 247 public LinkManager getLinkManager() 248 { 249 return m_linkManager; 250 } 251 252 //------------------------------------------------------------------ 253 // internals 254 //------------------------------------------------------------------ 255 256 /** 257 * General setup. 258 * @exception RemoteException if a remote error occurs 259 */ 260 protected synchronized void setupProxy() throws RemoteException 261 { 262 ProxyModel model = m_model.getProxyModel(); 263 URL proxy = model.getHost(); 264 if( null != proxy ) 265 { 266 PasswordAuthentication auth = model.getAuthentication(); 267 if( null != auth ) 268 { 269 TransitAuthenticator ta = new TransitAuthenticatorImpl( auth ); 270 RequestIdentifier id = model.getRequestIdentifier(); 271 DelegatingAuthenticator da = DelegatingAuthenticator.getInstance(); 272 da.addTransitAuthenticator( ta, id ); 273 } 274 275 int port = proxy.getPort(); 276 Properties system = System.getProperties(); 277 system.put( "http.proxyHost", proxy ); 278 system.put( "http.proxyPort", "" + port ); 279 String[] excludes = model.getExcludes(); 280 String path = toExcludesPath( excludes ); 281 if( null != path ) 282 { 283 system.put( "http.nonProxyHosts", path ); 284 } 285 } 286 } 287 288 /** 289 * Initialization of any sub-systems following the establishment of the initial 290 * transit system. As a general principal any subsystems that cannot be established 291 * for technical reasons (security or permission restrictions, etc.) should log 292 * an appropriate message and fallback to the initial setup thereby ensuring that 293 * an operable transit system is available. 294 * 295 * @exception IOException if an io error occurs 296 */ 297 protected void initialize() throws IOException 298 { 299 m_linkManager = new ArtifactLinkManager(); 300 initializeCache(); 301 } 302 303 /** 304 * Cache initialization. 305 * 306 * @exception IOException if an initialization error occurs 307 */ 308 private void initializeCache() throws IOException 309 { 310 getCacheHandler().initialize(); 311 } 312 313 /** 314 * Internal listener to the proxy model. 315 */ 316 private class ProxyController extends UnicastRemoteObject implements ProxyListener 317 { 318 /** 319 * Listener creation. 320 * @exception RemoteException if a remote error occurs 321 */ 322 public ProxyController() throws RemoteException 323 { 324 super(); 325 } 326 327 /** 328 * Notify a listener of the change to Transit proxy settings. 329 * @param event the proxy change event 330 */ 331 public void proxyChanged( ProxyEvent event ) 332 { 333 try 334 { 335 setupProxy(); 336 } 337 catch( RemoteException e ) 338 { 339 final String error = 340 "Unexpected error while attrempting to set proxy settings."; 341 getLogger().error( error, e ); 342 } 343 } 344 } 345 346 /** 347 * Internal listener to the proxy model. 348 */ 349 private class DisposalController extends UnicastRemoteObject implements DisposalListener 350 { 351 /** 352 * Listener creation. 353 * @exception RemoteException if a remote error occurs 354 */ 355 public DisposalController() throws RemoteException 356 { 357 super(); 358 } 359 360 /** 361 * Notify a listener of transit model disposal. 362 * @param event the disposal event 363 */ 364 public void notifyDisposal( DisposalEvent event ) 365 { 366 Thread thread = new Terminator(); 367 thread.start(); 368 } 369 } 370 371 /** 372 * Internal model terminator. 373 */ 374 private class Terminator extends Thread 375 { 376 Terminator() 377 { 378 } 379 380 /** 381 * Initiate model retraction from the RMI. 382 */ 383 public void run() 384 { 385 m_logger.debug( "initiating transit runtime disposal" ); 386 terminate( m_proxyController ); 387 terminate( m_cacheHandler ); 388 terminate( m_disposalController ); 389 m_logger.debug( "transit runtime disposal complete" ); 390 } 391 392 private void terminate( Object object ) 393 { 394 if( object instanceof Disposable ) 395 { 396 Disposable disposable = (Disposable) object; 397 disposable.dispose(); 398 } 399 if( object instanceof Remote ) 400 { 401 try 402 { 403 Remote remote = (Remote) object; 404 UnicastRemoteObject.unexportObject( remote, true ); 405 } 406 catch( NoSuchObjectException e ) 407 { 408 // ignore 409 } 410 catch( Throwable e ) 411 { 412 final String error = 413 "Unexpected error encountered during transit runtime termination."; 414 m_logger.warn( error, e ); 415 } 416 } 417 } 418 } 419 420 private Logger getLogger() 421 { 422 return m_logger; 423 } 424 425 //------------------------------------------------------------------ 426 // static (utils) 427 //------------------------------------------------------------------ 428 429 /** 430 * Resolve the list of host names to be assigned as non-proxied hosts. If proxy 431 * excludes are defined the string returned contains the host name (wilcards allowed) 432 * separated by the "|" character. If no proxy excludes are defined the value returned 433 * shall be null. The implementation reads the set of attribute names associated 434 * with a preferences node named "excludes". Each attribute name is appended to 435 * a single string where names are separated by the "|" character. 436 * 437 * @param names an array of named excludes 438 * @return a string containing a sequence of excluded hosts (possibly null) 439 */ 440 private static String toExcludesPath( String[] names ) 441 { 442 String spec = null; 443 for( int i=0; i < names.length; i++ ) 444 { 445 String name = names[i]; 446 if( null == spec ) 447 { 448 spec = name; 449 } 450 else 451 { 452 spec = spec + "|" + name; 453 } 454 } 455 return spec; 456 } 457 458 /** 459 * The namespace string for transit related properties. 460 */ 461 public static final String DOMAIN = "dpml.transit"; 462 463 /** 464 * The singleton transit context. 465 */ 466 private static SecuredTransitContext m_CONTEXT; 467 }